<?php
/*
 * PHP QR Code encoder Input encoding class Based on libqrencode C library
 * distributed under LGPL 2.1 Copyright (C) 2006, 2007, 2008, 2009 Kentaro
 * Fukuchi <fukuchi@megaui.net> PHP QR Code is distributed under LGPL 3
 * Copyright (C) 2010 Dominik Dzienia <deltalab at poczta dot fm> This library
 * is free software; you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or any later version. This
 * library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details. You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

define ( 'STRUCTURE_HEADER_BITS', 20 );
define ( 'MAX_STRUCTURED_SYMBOLS', 16 );
class QRinputItem {
	public $mode;
	public $size;
	public $data;
	public $bstream;
	public function __construct($mode, $size, $data, $bstream = null) {
		$setData = array_slice ( $data, 0, $size );
		
		if (count ( $setData ) < $size) {
			$setData = array_merge ( $setData, array_fill ( 0, $size - count ( $setData ), 0 ) );
		}
		
		if (! QRinput::check ( $mode, $size, $setData )) {
			throw new Exception ( 'Error m:' . $mode . ',s:' . $size . ',d:' . join ( ',', $setData ) );
			return null;
		}
		
		$this->mode = $mode;
		$this->size = $size;
		$this->data = $setData;
		$this->bstream = $bstream;
	}
	
	// ----------------------------------------------------------------------
	public function encodeModeNum($version) {
		try {
			
			$words = ( int ) ($this->size / 3);
			$bs = new QRbitstream ();
			
			$val = 0x1;
			$bs->appendNum ( 4, $val );
			$bs->appendNum ( QRspec::lengthIndicator ( QR_MODE_NUM, $version ), $this->size );
			
			for($i = 0; $i < $words; $i ++) {
				$val = (ord ( $this->data [$i * 3] ) - ord ( '0' )) * 100;
				$val += (ord ( $this->data [$i * 3 + 1] ) - ord ( '0' )) * 10;
				$val += (ord ( $this->data [$i * 3 + 2] ) - ord ( '0' ));
				$bs->appendNum ( 10, $val );
			}
			
			if ($this->size - $words * 3 == 1) {
				$val = ord ( $this->data [$words * 3] ) - ord ( '0' );
				$bs->appendNum ( 4, $val );
			} else if ($this->size - $words * 3 == 2) {
				$val = (ord ( $this->data [$words * 3] ) - ord ( '0' )) * 10;
				$val += (ord ( $this->data [$words * 3 + 1] ) - ord ( '0' ));
				$bs->appendNum ( 7, $val );
			}
			
			$this->bstream = $bs;
			return 0;
		} catch ( Exception $e ) {
			return - 1;
		}
	}
	
	// ----------------------------------------------------------------------
	public function encodeModeAn($version) {
		try {
			$words = ( int ) ($this->size / 2);
			$bs = new QRbitstream ();
			
			$bs->appendNum ( 4, 0x02 );
			$bs->appendNum ( QRspec::lengthIndicator ( QR_MODE_AN, $version ), $this->size );
			
			for($i = 0; $i < $words; $i ++) {
				$val = ( int ) QRinput::lookAnTable ( ord ( $this->data [$i * 2] ) ) * 45;
				$val += ( int ) QRinput::lookAnTable ( ord ( $this->data [$i * 2 + 1] ) );
				
				$bs->appendNum ( 11, $val );
			}
			
			if ($this->size & 1) {
				$val = QRinput::lookAnTable ( ord ( $this->data [$words * 2] ) );
				$bs->appendNum ( 6, $val );
			}
			
			$this->bstream = $bs;
			return 0;
		} catch ( Exception $e ) {
			return - 1;
		}
	}
	
	// ----------------------------------------------------------------------
	public function encodeMode8($version) {
		try {
			$bs = new QRbitstream ();
			
			$bs->appendNum ( 4, 0x4 );
			$bs->appendNum ( QRspec::lengthIndicator ( QR_MODE_8, $version ), $this->size );
			
			for($i = 0; $i < $this->size; $i ++) {
				$bs->appendNum ( 8, ord ( $this->data [$i] ) );
			}
			
			$this->bstream = $bs;
			return 0;
		} catch ( Exception $e ) {
			return - 1;
		}
	}
	
	// ----------------------------------------------------------------------
	public function encodeModeKanji($version) {
		try {
			
			$bs = new QRbitrtream ();
			
			$bs->appendNum ( 4, 0x8 );
			$bs->appendNum ( QRspec::lengthIndicator ( QR_MODE_KANJI, $version ), ( int ) ($this->size / 2) );
			
			for($i = 0; $i < $this->size; $i += 2) {
				$val = (ord ( $this->data [$i] ) << 8) | ord ( $this->data [$i + 1] );
				if ($val <= 0x9ffc) {
					$val -= 0x8140;
				} else {
					$val -= 0xc140;
				}
				
				$h = ($val >> 8) * 0xc0;
				$val = ($val & 0xff) + $h;
				
				$bs->appendNum ( 13, $val );
			}
			
			$this->bstream = $bs;
			return 0;
		} catch ( Exception $e ) {
			return - 1;
		}
	}
	
	// ----------------------------------------------------------------------
	public function encodeModeStructure() {
		try {
			$bs = new QRbitstream ();
			
			$bs->appendNum ( 4, 0x03 );
			$bs->appendNum ( 4, ord ( $this->data [1] ) - 1 );
			$bs->appendNum ( 4, ord ( $this->data [0] ) - 1 );
			$bs->appendNum ( 8, ord ( $this->data [2] ) );
			
			$this->bstream = $bs;
			return 0;
		} catch ( Exception $e ) {
			return - 1;
		}
	}
	
	// ----------------------------------------------------------------------
	public function estimateBitStreamSizeOfEntry($version) {
		$bits = 0;
		
		if ($version == 0)
			$version = 1;
		
		switch ($this->mode) {
			case QR_MODE_NUM :
				$bits = QRinput::estimateBitsModeNum ( $this->size );
				break;
			case QR_MODE_AN :
				$bits = QRinput::estimateBitsModeAn ( $this->size );
				break;
			case QR_MODE_8 :
				$bits = QRinput::estimateBitsMode8 ( $this->size );
				break;
			case QR_MODE_KANJI :
				$bits = QRinput::estimateBitsModeKanji ( $this->size );
				break;
			case QR_MODE_STRUCTURE :
				return STRUCTURE_HEADER_BITS;
			default :
				return 0;
		}
		
		$l = QRspec::lengthIndicator ( $this->mode, $version );
		$m = 1 << $l;
		$num = ( int ) (($this->size + $m - 1) / $m);
		
		$bits += $num * (4 + $l);
		
		return $bits;
	}
	
	// ----------------------------------------------------------------------
	public function encodeBitStream($version) {
		try {
			
			unset ( $this->bstream );
			$words = QRspec::maximumWords ( $this->mode, $version );
			
			if ($this->size > $words) {
				
				$st1 = new QRinputItem ( $this->mode, $words, $this->data );
				$st2 = new QRinputItem ( $this->mode, $this->size - $words, array_slice ( $this->data, $words ) );
				
				$st1->encodeBitStream ( $version );
				$st2->encodeBitStream ( $version );
				
				$this->bstream = new QRbitstream ();
				$this->bstream->append ( $st1->bstream );
				$this->bstream->append ( $st2->bstream );
				
				unset ( $st1 );
				unset ( $st2 );
			} else {
				
				$ret = 0;
				
				switch ($this->mode) {
					case QR_MODE_NUM :
						$ret = $this->encodeModeNum ( $version );
						break;
					case QR_MODE_AN :
						$ret = $this->encodeModeAn ( $version );
						break;
					case QR_MODE_8 :
						$ret = $this->encodeMode8 ( $version );
						break;
					case QR_MODE_KANJI :
						$ret = $this->encodeModeKanji ( $version );
						break;
					case QR_MODE_STRUCTURE :
						$ret = $this->encodeModeStructure ();
						break;
					
					default :
						break;
				}
				
				if ($ret < 0)
					return - 1;
			}
			
			return $this->bstream->size ();
		} catch ( Exception $e ) {
			return - 1;
		}
	}
}
;

// ##########################################################################
class QRinput {
	public $items;
	private $version;
	private $level;
	
	// ----------------------------------------------------------------------
	public function __construct($version = 0, $level = QR_ECLEVEL_L) {
		if ($version < 0 || $version > QRSPEC_VERSION_MAX || $level > QR_ECLEVEL_H) {
			throw new Exception ( 'Invalid version no' );
			return NULL;
		}
		
		$this->version = $version;
		$this->level = $level;
	}
	
	// ----------------------------------------------------------------------
	public function getVersion() {
		return $this->version;
	}
	
	// ----------------------------------------------------------------------
	public function setVersion($version) {
		if ($version < 0 || $version > QRSPEC_VERSION_MAX) {
			throw new Exception ( 'Invalid version no' );
			return - 1;
		}
		
		$this->version = $version;
		
		return 0;
	}
	
	// ----------------------------------------------------------------------
	public function getErrorCorrectionLevel() {
		return $this->level;
	}
	
	// ----------------------------------------------------------------------
	public function setErrorCorrectionLevel($level) {
		if ($level > QR_ECLEVEL_H) {
			throw new Exception ( 'Invalid ECLEVEL' );
			return - 1;
		}
		
		$this->level = $level;
		
		return 0;
	}
	
	// ----------------------------------------------------------------------
	public function appendEntry(QRinputItem $entry) {
		$this->items [] = $entry;
	}
	
	// ----------------------------------------------------------------------
	public function append($mode, $size, $data) {
		try {
			$entry = new QRinputItem ( $mode, $size, $data );
			$this->items [] = $entry;
			return 0;
		} catch ( Exception $e ) {
			return - 1;
		}
	}
	
	// ----------------------------------------------------------------------
	public function insertStructuredAppendHeader($size, $index, $parity) {
		if ($size > MAX_STRUCTURED_SYMBOLS) {
			throw new Exception ( 'insertStructuredAppendHeader wrong size' );
		}
		
		if ($index <= 0 || $index > MAX_STRUCTURED_SYMBOLS) {
			throw new Exception ( 'insertStructuredAppendHeader wrong index' );
		}
		
		$buf = array (
				$size,
				$index,
				$parity 
		);
		
		try {
			$entry = new QRinputItem ( QR_MODE_STRUCTURE, 3, buf );
			array_unshift ( $this->items, $entry );
			return 0;
		} catch ( Exception $e ) {
			return - 1;
		}
	}
	
	// ----------------------------------------------------------------------
	public function calcParity() {
		$parity = 0;
		
		foreach ( $this->items as $item ) {
			if ($item->mode != QR_MODE_STRUCTURE) {
				for($i = $item->size - 1; $i >= 0; $i --) {
					$parity ^= $item->data [$i];
				}
			}
		}
		
		return $parity;
	}
	
	// ----------------------------------------------------------------------
	public static function checkModeNum($size, $data) {
		for($i = 0; $i < $size; $i ++) {
			if ((ord ( $data [$i] ) < ord ( '0' )) || (ord ( $data [$i] ) > ord ( '9' ))) {
				return false;
			}
		}
		
		return true;
	}
	
	// ----------------------------------------------------------------------
	public static function estimateBitsModeNum($size) {
		$w = ( int ) $size / 3;
		$bits = $w * 10;
		
		switch ($size - $w * 3) {
			case 1 :
				$bits += 4;
				break;
			case 2 :
				$bits += 7;
				break;
			default :
				break;
		}
		
		return $bits;
	}
	
	// ----------------------------------------------------------------------
	public static $anTable = array (
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			36,
			- 1,
			- 1,
			- 1,
			37,
			38,
			- 1,
			- 1,
			- 1,
			- 1,
			39,
			40,
			- 1,
			41,
			42,
			43,
			0,
			1,
			2,
			3,
			4,
			5,
			6,
			7,
			8,
			9,
			44,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			10,
			11,
			12,
			13,
			14,
			15,
			16,
			17,
			18,
			19,
			20,
			21,
			22,
			23,
			24,
			25,
			26,
			27,
			28,
			29,
			30,
			31,
			32,
			33,
			34,
			35,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1,
			- 1 
	);
	
	// ----------------------------------------------------------------------
	public static function lookAnTable($c) {
		return (($c > 127) ? - 1 : self::$anTable [$c]);
	}
	
	// ----------------------------------------------------------------------
	public static function checkModeAn($size, $data) {
		for($i = 0; $i < $size; $i ++) {
			if (self::lookAnTable ( ord ( $data [$i] ) ) == - 1) {
				return false;
			}
		}
		
		return true;
	}
	
	// ----------------------------------------------------------------------
	public static function estimateBitsModeAn($size) {
		$w = ( int ) ($size / 2);
		$bits = $w * 11;
		
		if ($size & 1) {
			$bits += 6;
		}
		
		return $bits;
	}
	
	// ----------------------------------------------------------------------
	public static function estimateBitsMode8($size) {
		return $size * 8;
	}
	
	// ----------------------------------------------------------------------
	public function estimateBitsModeKanji($size) {
		return ( int ) (($size / 2) * 13);
	}
	
	// ----------------------------------------------------------------------
	public static function checkModeKanji($size, $data) {
		if ($size & 1)
			return false;
		
		for($i = 0; $i < $size; $i += 2) {
			$val = (ord ( $data [$i] ) << 8) | ord ( $data [$i + 1] );
			if ($val < 0x8140 || ($val > 0x9ffc && $val < 0xe040) || $val > 0xebbf) {
				return false;
			}
		}
		
		return true;
	}
	
	/**
	 * *********************************************************************
	 * Validation
	 * ********************************************************************
	 */
	public static function check($mode, $size, $data) {
		if ($size <= 0)
			return false;
		
		switch ($mode) {
			case QR_MODE_NUM :
				return self::checkModeNum ( $size, $data );
				break;
			case QR_MODE_AN :
				return self::checkModeAn ( $size, $data );
				break;
			case QR_MODE_KANJI :
				return self::checkModeKanji ( $size, $data );
				break;
			case QR_MODE_8 :
				return true;
				break;
			case QR_MODE_STRUCTURE :
				return true;
				break;
			
			default :
				break;
		}
		
		return false;
	}
	
	// ----------------------------------------------------------------------
	public function estimateBitStreamSize($version) {
		$bits = 0;
		
		foreach ( $this->items as $item ) {
			$bits += $item->estimateBitStreamSizeOfEntry ( $version );
		}
		
		return $bits;
	}
	
	// ----------------------------------------------------------------------
	public function estimateVersion() {
		$version = 0;
		$prev = 0;
		do {
			$prev = $version;
			$bits = $this->estimateBitStreamSize ( $prev );
			$version = QRspec::getMinimumVersion ( ( int ) (($bits + 7) / 8), $this->level );
			if ($version < 0) {
				return - 1;
			}
		} while ( $version > $prev );
		
		return $version;
	}
	
	// ----------------------------------------------------------------------
	public static function lengthOfCode($mode, $version, $bits) {
		$payload = $bits - 4 - QRspec::lengthIndicator ( $mode, $version );
		switch ($mode) {
			case QR_MODE_NUM :
				$chunks = ( int ) ($payload / 10);
				$remain = $payload - $chunks * 10;
				$size = $chunks * 3;
				if ($remain >= 7) {
					$size += 2;
				} else if ($remain >= 4) {
					$size += 1;
				}
				break;
			case QR_MODE_AN :
				$chunks = ( int ) ($payload / 11);
				$remain = $payload - $chunks * 11;
				$size = $chunks * 2;
				if ($remain >= 6)
					$size ++;
				break;
			case QR_MODE_8 :
				$size = ( int ) ($payload / 8);
				break;
			case QR_MODE_KANJI :
				$size = ( int ) (($payload / 13) * 2);
				break;
			case QR_MODE_STRUCTURE :
				$size = ( int ) ($payload / 8);
				break;
			default :
				$size = 0;
				break;
		}
		
		$maxsize = QRspec::maximumWords ( $mode, $version );
		if ($size < 0)
			$size = 0;
		if ($size > $maxsize)
			$size = $maxsize;
		
		return $size;
	}
	
	// ----------------------------------------------------------------------
	public function createBitStream() {
		$total = 0;
		
		foreach ( $this->items as $item ) {
			$bits = $item->encodeBitStream ( $this->version );
			
			if ($bits < 0)
				return - 1;
			
			$total += $bits;
		}
		
		return $total;
	}
	
	// ----------------------------------------------------------------------
	public function convertData() {
		$ver = $this->estimateVersion ();
		if ($ver > $this->getVersion ()) {
			$this->setVersion ( $ver );
		}
		
		for(;;) {
			$bits = $this->createBitStream ();
			
			if ($bits < 0)
				return - 1;
			
			$ver = QRspec::getMinimumVersion ( ( int ) (($bits + 7) / 8), $this->level );
			if ($ver < 0) {
				throw new Exception ( 'WRONG VERSION' );
				return - 1;
			} else if ($ver > $this->getVersion ()) {
				$this->setVersion ( $ver );
			} else {
				break;
			}
		}
		
		return 0;
	}
	
	// ----------------------------------------------------------------------
	public function appendPaddingBit(&$bstream) {
		$bits = $bstream->size ();
		$maxwords = QRspec::getDataLength ( $this->version, $this->level );
		$maxbits = $maxwords * 8;
		
		if ($maxbits == $bits) {
			return 0;
		}
		
		if ($maxbits - $bits < 5) {
			return $bstream->appendNum ( $maxbits - $bits, 0 );
		}
		
		$bits += 4;
		$words = ( int ) (($bits + 7) / 8);
		
		$padding = new QRbitstream ();
		$ret = $padding->appendNum ( $words * 8 - $bits + 4, 0 );
		
		if ($ret < 0)
			return $ret;
		
		$padlen = $maxwords - $words;
		
		if ($padlen > 0) {
			
			$padbuf = array ();
			for($i = 0; $i < $padlen; $i ++) {
				$padbuf [$i] = ($i & 1) ? 0x11 : 0xec;
			}
			
			$ret = $padding->appendBytes ( $padlen, $padbuf );
			
			if ($ret < 0)
				return $ret;
		}
		
		$ret = $bstream->append ( $padding );
		
		return $ret;
	}
	
	// ----------------------------------------------------------------------
	public function mergeBitStream() {
		if ($this->convertData () < 0) {
			return null;
		}
		
		$bstream = new QRbitstream ();
		
		foreach ( $this->items as $item ) {
			$ret = $bstream->append ( $item->bstream );
			if ($ret < 0) {
				return null;
			}
		}
		
		return $bstream;
	}
	
	// ----------------------------------------------------------------------
	public function getBitStream() {
		$bstream = $this->mergeBitStream ();
		
		if ($bstream == null) {
			return null;
		}
		
		$ret = $this->appendPaddingBit ( $bstream );
		if ($ret < 0) {
			return null;
		}
		
		return $bstream;
	}
	
	// ----------------------------------------------------------------------
	public function getByteStream() {
		$bstream = $this->getBitStream ();
		if ($bstream == null) {
			return null;
		}
		
		return $bstream->toByte ();
	}
}
        
        
    